//	Copyright (c) 2012 Jonathan Loe
//
//	MIT license (http://en.wikipedia.org/wiki/MIT_License)
//
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights 
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//	of the Software, and to permit persons to whom the Software is furnished to do so, 
//	subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in all 
//	copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
//	PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
//	FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Text.RegularExpressions;

namespace Ordinaire.Net
{
	/// <summary>
	/// Email class handles email-related processes.
	/// </summary>
	public class Email
	{
		#region Variables
		public delegate void SendDelegate();

		private SmtpClient client = null;
		private MailMessage mail = null;

		private KeyValuePair<string, string> _from;
		private Dictionary<string, string> _to;
		private Dictionary<string, object> _attachments;
		#endregion

		#region Constructor
		/// <summary>
		/// Initializes empty instance of Email class.
		/// </summary>
		private Email()
		{
			mail = new MailMessage();
		}

		/// <summary>
		/// Initializes instance of Email class with specified Smtp server hostname.
		/// </summary>
		/// <param name="host">hostname</param>
		public Email(string host) : this()
		{
			if (String.IsNullOrEmpty(host)) { throw new ArgumentNullException("host"); }

			client = new SmtpClient(host);
		}

		/// <summary>
		/// Initializes instance of Emal class with specified Smtp server hostname and port.
		/// </summary>
		/// <param name="host">hostname</param>
		/// <param name="port">port number</param>
		public Email(string host, int port) : this()
		{
			if (String.IsNullOrEmpty(host)) { throw new ArgumentNullException("host"); }

			client = new SmtpClient(host, port);
		}
		#endregion

		#region Property methods
		#region Mail related...
		/// <summary>
		/// Gets or sets the sender of the message.
		/// Key and value refer to the sender address and display name respectively.
		/// </summary>
		public KeyValuePair<string, string> From
		{
			get { return _from; }
			set { _from = value; }
		}

		/// <summary>
		/// Gets collection of addresses that contains the recipients of the email.
		/// </summary>
		public Dictionary<string, string> To
		{
			get
			{
				if (_to == null) { _to = new Dictionary<string, string>(); }
				return _to;
			}
		}

		/// <summary>
		/// Gets or sets the subject line of the message.
		/// </summary>
		public string Subject
		{
			get { return mail.Subject; }
			set { mail.Subject = value; }
		}

		/// <summary>
		/// Gets or sets message body.
		/// </summary>
		public string Body
		{
			get { return mail.Body; }
			set { mail.Body = value; }
		}

		/// <summary>
		/// Gets or sets whether message body is in Html.
		/// </summary>
		public bool IsBodyHtml
		{
			get { return mail.IsBodyHtml; }
			set { mail.IsBodyHtml = value; }
		}

		/// <summary>
		/// Gets or sets a list of attachments.
		/// </summary>
		public Dictionary<string, object> Attachments
		{
			get
			{
				if (_attachments == null) _attachments = new Dictionary<string, object>();
				return _attachments;
			}
		}
		#endregion

		#region Smtp related...
		/// <summary>
		/// Enables or disables Ssl.
		/// </summary>
		public bool EnableSsl
		{
			get { return client.EnableSsl; }
			set { client.EnableSsl = value; }
		}

		/// <summary>
		/// Specifies how email is delivered.
		/// </summary>
		public SmtpDeliveryMethod DeliveryMethod
		{
			get { return client.DeliveryMethod; }
			set { client.DeliveryMethod = value; }
		}

		/// <summary>
		/// Gets or sets a credential to send the email if any.
		/// </summary>
		public ICredentialsByHost Credential
		{
			get { return client.Credentials; }
			set { client.Credentials = value; }
		}
		#endregion
		#endregion

		#region Public methods
		#region Static methods
		/// <summary>
		/// Initializes instance of Email class with a predefined attributes for Gmail account.
		/// </summary>
		/// <param name="username">username</param>
		/// <param name="password">password</param>
		/// <returns>Email class instance</returns>
		public static Email CreateGmailInstance(string username, string password)
		{
			Email mail = new Email("smtp.gmail.com", 587);
			mail.EnableSsl = true;
			mail.DeliveryMethod = SmtpDeliveryMethod.Network;
			mail.Credential = new NetworkCredential(username, password);
			return mail;
		}
		#endregion

		/// <summary>
		/// Sets message body using a specified template and set of messages to insert.
		/// </summary>
		/// <param name="file">path of the template file</param>
		/// <param name="toReplace">list of messages to insert</param>
		public void SetMessageBodyFromTemplate(string file, Dictionary<string, string> toReplace)
		{
			StreamReader reader = new StreamReader(file);
			mail.Body = reader.ReadToEnd();

			// Replace defined quotes with the specified messages
			foreach (KeyValuePair<string, string> kvp in toReplace)
				mail.Body = mail.Body.Replace(kvp.Key, kvp.Value);

			reader.Close();
		}

		/// <summary>
		/// Send the email.
		/// </summary>
		public void Send()
		{
			if (_from.Equals(null) || String.IsNullOrEmpty(_from.Key))
				throw new ArgumentException(ExceptionMessage.SenderNotSpecified);

			if (_to == null || _to.Count == 0)
				throw new ArgumentException(ExceptionMessage.ReceiverNotSpecified);

			mail.From = new MailAddress(_from.Key, _from.Value);
			foreach (KeyValuePair<string, string> kvp in _to)
				mail.To.Add(new MailAddress(kvp.Key, kvp.Value));

			// Put attachments to the email
			foreach (KeyValuePair<string, object> attachment in _attachments)
			{
				if (attachment.Value is Stream) mail.Attachments.Add(new Attachment((Stream)attachment.Value, attachment.Key));
				else if (attachment.Value is string) mail.Attachments.Add(new Attachment((string)attachment.Value));
			}

			client.Send(mail);
		}

		/// <summary>
		/// Send this email asynchronously.
		/// </summary>
		public void SendAsync()
		{
			SendDelegate sd = new SendDelegate(this.Send);
			AsyncCallback ac = new AsyncCallback(this.SendOnCallback);
			IAsyncResult ar = sd.BeginInvoke(ac, null);
		}
		#endregion

		#region Private methods
		/// <summary>
		/// Handles the callback event of asynchronous email send.
		/// </summary>
		/// <param name="ar">the status of asynchronous operation</param>
		private void SendOnCallback(IAsyncResult ar)
		{
			SendDelegate del = (SendDelegate)((AsyncResult)ar).AsyncDelegate;
			del.EndInvoke(ar);
		}
		#endregion
	}
}
